#ifndef __M_ROOM_H__
#define __M_ROOM_H__

#include <vector>
#include "util.hpp"
#include "logger.hpp"
#include "db.hpp"
#include "online.hpp"

// 房间管理类 - 为匹配成功的用户建立一个小的关联关系
// 游戏房间有两种请求 - 二者都是通过发送到服务器，再由服务器推送给两个在房间中的用户，因为都要让双方看到变化
// 1. 下棋请求
// 2. 聊天请求

// 描述房间状态
// 不同状态决定了不同情况
// 游戏进行中的退出 和 游戏正常结束
typedef enum
{
    GAME_START,
    GAME_OVER
} room_state;

#define BOARD_ROW 15
#define BOARD_COL 15

#define CHESS_WHITE 1
#define CHESS_BLACK 2

class room
{
public:
    room(int room_id, user_table *ut, online_manager *om)
        : _room_id(room_id), _state(GAME_START), _player_num(0), _ut(ut), _om(om), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        DLOG("%d号游戏房间创建成功!", _room_id);
    }

    ~room()
    {
        DLOG("%d号游戏房间销毁成功!", _room_id);
    }

    int get_room_id() { return _room_id; }

    room_state get_room_state() { return _state; }

    int get_player_num() { return _player_num; }

    void add_white_id(int uid)
    {
        std::unique_lock<std::mutex> lock(_mtx);
        _white_id = uid;
        _player_num++;
    }

    void add_balck_id(int uid)
    {
        std::unique_lock<std::mutex> lock(_mtx);
        _black_id = uid;
        _player_num++;
    }

    int get_white_user() { return _white_id; }

    int get_black_user() { return _black_id; }

    // 根据不同的请求(下棋，聊天，退出)，调用下面不同的接口
    void handler_request(Json::Value &req)
    {
        // 1. 判断请求中的房间号是否与本房间的一致，如果不一致则构建响应并广播
        Json::Value resp;
        if (req["room_id"].asInt() != _room_id)
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "房间号不匹配!";
            broadcast(resp);
            return;
        }

        // 2. 如果房间号没问题，接下来就是判断是哪种请求，根据不同请求构建响应
        if (req["optype"].asString() == "put_chess")
        {
            // 交对应的处理下棋请求的函数，返回值也是一个resp
            resp = handler_chess(req);

            // 如果resp的"winner"不为0，表示胜负已分(响应报文协议规定：0-未分胜负，!0-已分胜负)，并且"winner"的值就是胜方的uid
            if (resp["winner"].asInt() != 0)
            {
                // 表示用户下完这步棋就赢了，则需要更新数据库信息
                int winner_id = resp["winner"].asInt();
                int loser_id = (winner_id == _white_id) ? _black_id : _white_id;

                // 更新数据库信息
                _ut->win(winner_id);
                _ut->lose(loser_id);

                // 更新房间状态(需要加锁，_state也是可能产生线程安全问题的共享资源)
                std::unique_lock<std::mutex> lock(_mtx);
                _state = GAME_OVER;
            }
        }
        else if (req["optype"].asString() == "chat")
        {
            // 对于聊天请求的处理就比较简单了，不需要更新房间状态信息以及数据库的信息
            resp = handler_chat(req);
        }
        else
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "未知请求类型!";
        }
        broadcast(resp); // 广播给房间中的用户
        return;
    }

    Json::Value handler_chess(Json::Value &req)
    {
        // 由于响应字段包含请求字段，所以可以使用req来先初始化resp
        Json::Value resp = req;

        // 1. 取出请求中的uid，row和col
        int cur_uid = req["uid"].asInt();
        int cur_row = req["row"].asInt();
        int cur_col = req["col"].asInt();

        // 2. 这里存在用户刚下完棋，其中一方关闭前端网页，关闭连接了，所以这里要判断双方是否online
        bool ret1 = _om->is_in_game_room(_white_id);
        if (ret1 == false)
        {
            // 白方已退出房间
            resp["result"] = true;
            resp["reason"] = "运气真好，对方退出，不战而胜!";
            resp["winner"] = _black_id;
            return resp;
        }

        bool ret2 = _om->is_in_game_room(_black_id);
        if (ret2 == false)
        {
            // 白方已退出房间
            resp["result"] = true;
            resp["reason"] = "运气真好，对方退出，不战而胜!";
            resp["winner"] = _white_id;
            return resp;
        }

        // 3. 判断下棋位置是否合理(客户端下的位置是否被占用)
        int color = (cur_uid == _white_id) ? CHESS_WHITE : CHESS_BLACK;
        {
            std::unique_lock<std::mutex> lock(_mtx);
            if (_board[cur_row][cur_col] != 0)
            {
                // 位置被占了
                resp["result"] = false;
                resp["reason"] = "您所下的位置已有棋子!";
                return resp;
            }
            _board[cur_row][cur_col] = color; // 下棋
        }

        // 4. 判断是否有玩家胜利(准确来说是判断下棋方下完这颗棋子是否获胜)
        if (check_win(cur_row, cur_col, color))
        {
            resp["result"] = true;
            resp["reason"] = "恭喜您已获得胜利!";
            resp["winner"] = cur_uid;
            return resp;
        }
        else
        {
            resp["result"] = true;
            resp["reason"] = "未分胜负!";
            resp["winner"] = 0; // 0表示无人胜出
            return resp;
        }
    }

    Json::Value handler_chat(Json::Value &req)
    {
        // 由于响应字段包含请求字段，所以可以使用req来先初始化resp
        Json::Value resp = req;

        // 这里可以在设计一个接口处理聊天中是否包含敏感词汇，这里简单以是否包含"垃圾"这一词为例
        size_t pos = req["message"].asString().find("垃圾");
        if (pos != std::string::npos)
        {
            resp["result"] = false;
            resp["reason"] = "无法发送敏感信息!";
            return resp;
        }

        // 如果正常则返回消息
        resp["result"] = true;
        return resp;
    }

    // 退出房间的借口并不是请求到来时去调用的，而是当用户连接断开时才会去调用这个接口(这个接口和上面两个处理逻辑是不一样的，返回值是void，参数是uid)
    void handler_exit(int uid)
    {
        // 1. 如果中途有人率先退出，这个接口是谁退出谁调用的，所以先调用了这个接口的玩家就是败方
        // 2. 中途败方调用完之后，需要更改房间状态，因为胜方也需要调用这个接口，此时不应该走到if中去，应该执行下面的_player_num--的操作
        Json::Value resp;

        std::unique_lock<std::mutex> lock(_mtx);
        if (_state == GAME_START)
        {
            // 如果中途有人退出，此时房间状态依然还是GAME_START的
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["reason"] = "运气真好，对方退出，不战而胜!";
            resp["room_id"] = _room_id;
            resp["uid"] = uid; // 当前退出操作是谁发起的
            resp["row"] = -1;
            resp["col"] = -1;
            resp["winner"] = (uid == _black_id) ? _white_id : _black_id;
            int winner_id = (uid == _black_id) ? _white_id : _black_id;
            int loser_id = uid;

            _ut->win(winner_id);
            _ut->lose(loser_id);

            _state = GAME_OVER;
            broadcast(resp);
        }
        _player_num--;
        return;
    }

    // 对响应进行广播
    void broadcast(Json::Value &resp)
    {
        // 通过id来拿到websocket通信的句柄，也就是conn智能指针
        // 通过调用connection类里面的send函数就可以在websocket连接上，发送已经序列化好的body数据

        std::string body;
        // 1. 对响应进行序列化为Json格式字符串
        json_util::serialize(resp, body);

        // 2. 获取房间中所有用户的通信连接并发送消息
        wsserver_t::connection_ptr wconn = _om->get_conn_from_room(_white_id);
        if (wconn.get() != nullptr)
            wconn->send(body);

        wsserver_t::connection_ptr bconn = _om->get_conn_from_room(_black_id);
        if (bconn != nullptr)
            bconn->send(body);
        return;
    }

private:
    bool check_win(int row, int col, int chess_color)
    {
        // 检查横，竖，正斜线，反斜线是否有五颗棋子
        return check(row, col, 1, 0, chess_color) || check(row, col, 0, 1, chess_color) ||
               check(row, col, 1, 1, chess_color) || check(row, col, -1, 1, chess_color);
    }

    bool check(int row, int col, int offset_row, int offset_col, int color)
    {
        int ret = 1; // 当前位置算一颗
        int cur_row = row + offset_row;
        int cur_col = col + offset_col;

        {
            std::unique_lock<std::mutex> lock(_mtx);
            while (cur_row < BOARD_ROW && cur_col < BOARD_COL && cur_row >= 0 && cur_col >= 0 && _board[cur_row][cur_col] == color)
            {
                ret++;
                cur_row += offset_row;
                cur_col += offset_col;
            }
        }

        cur_row = row - offset_row;
        cur_col = col - offset_col;
        {
            std::unique_lock<std::mutex> lock(_mtx);
            while (cur_row < BOARD_ROW && cur_col < BOARD_COL && cur_row >= 0 && cur_col >= 0 && _board[cur_row][cur_col] == color)
            {
                ret++;
                cur_row -= offset_row;
                cur_col -= offset_col;
            }
        }

        return ret >= 5;
    }

private:
    int _room_id;      // 房间标识符
    room_state _state; // 房间状态

    int _player_num; // 房间中玩家数量
    int _white_id;   // 先进入房间的分配白棋
    int _black_id;   // 后进入房间的分配黑棋

    // 除了上面的几个信息外，剩下的就是一些句柄了
    // 当游戏房间中胜负已分时，我们要更新数据库中两个用户的信息，所以还需要数据管理模块的句柄
    // 在用户发起聊天或下棋请求时，我们要判断用户此时是否在线,同时还要将请求处理后的响应广播给房间中的所有用户
    // 那么就还需要两个用户的websocket连接
    // 这两个需求的实现，离不开在线用户管理模块句柄，因为在线用户管理模块提供的两个最主要的功能就是判断用户是否在线 和 获取用户对应的websocket连接
    user_table *_ut;
    online_manager *_om;

    std::vector<std::vector<int>> _board; // 棋盘
    std::mutex _mtx;                      // 保护共享资源
};

// 因为将来的房间肯定是有多个的，所以根据先描述，再组织的思想
// 有了上面的room类，我们就可以来实现一个房间管理的类了
// 因为room类的构造函数需要user_table和online_manager两个句柄，所以这里也需要这两个句柄

// 其次，我们需要构建房间号和房间的关系，所以需要一个哈希表，一个房间号的分配器(类似之前session_id的分配器)
// 当然哈希表不能直接存储房间对象，要不然需要的空间太大了
// 我们就存储管理房间对象的智能指针，这个智能指针也必须是shared_ptr
// 主要是因为在向哈希表中插入键值对的时候，会发生智能指针的拷贝，所以我们只能使用shared_ptr

// 当然，由于服务器模块都是根据uid来获取各种信息，理论上我们也应该维护一张uid和room的映射关系，但其实没必要
// 我们只需要再维护一张uid和rid的哈希表就可以了，通过uid找到rid就能找到room了

// 当然还需要一把锁，保护两张哈希表和房间号分配器
// 因为user_table和online_manager这两个句柄当初已经是实现成线程安全的了

using room_ptr = std::shared_ptr<room>;

class room_manager
{
public:
    room_manager(user_table *ut, online_manager *om)
        : _next_rid(1), _ut(ut), _om(om)
    {
        DLOG("房间管理模块初始化完毕!");
    }

    ~room_manager()
    {
        DLOG("房间管理模块销毁完毕!");
    }

    // 将来两个玩家匹配成功后，需要为他们分配房间，所以需要一个创建房间的接口，并返回他们的room_ptr句柄
    room_ptr create_room(int uid1, int uid2)
    {
        // 1. 两个用户在游戏大厅会有一个长连接，匹配成功后，进入游戏房间后，会重新与服务器建立websocket长连接
        //    这两个长连接各有各的功能，互不干扰
        //    游戏大厅的长连接负责对战匹配
        //    游戏房间的长连接负责下棋聊天等业务
        // 2. 所以，我们首先需要甄别这俩用户是否都在游戏大厅，同时都在游戏大厅，我们才会为其创建房间
        if (!_om->is_in_game_hall(uid1))
        {
            DLOG("用户%d不在游戏大厅中, 创建失败!", uid1);
            return room_ptr();
        }
        if (!_om->is_in_game_hall(uid2))
        {
            DLOG("用户%d不在游戏大厅中, 创建失败!", uid2);
            return room_ptr();
        }

        // 3. 创建房间
        std::unique_lock<std::mutex> lock(_mtx);    // 需要加锁保护哈希表
        room_ptr rp(new room(_next_rid, _ut, _om)); // 这个智能指针是在栈上开辟的，出了这个函数就会销毁
        rp->add_balck_id(uid1);
        rp->add_white_id(uid2);

        DLOG("用户1_id: %d, 棋子id: %d", uid1, rp->get_black_user());
        DLOG("用户2_id: %d, 棋子id: %d", uid2, rp->get_white_user());

        // 4. 将创建好的房间管理起来
        _rooms.insert(std::make_pair(_next_rid, rp));
        _uid_rid.insert(std::make_pair(uid1, _next_rid));
        _uid_rid.insert(std::make_pair(uid2, _next_rid));
        _next_rid++;

        // 5. 返回句柄
        return rp;
    }

    // 通过rid获取room_ptr
    room_ptr get_room_by_rid(int rid)
    {
        std::unique_lock<std::mutex> lock(_mtx); // 需要加锁保护哈希表
        auto iter = _rooms.find(rid);
        if (iter == _rooms.end())
            return room_ptr();
        return iter->second;
    }

    // 通过uid获取room_ptr
    room_ptr get_room_by_uid(int uid)
    {
        std::unique_lock<std::mutex> lock(_mtx); // 需要加锁保护哈希表
        // 1. 通过uid获取rid
        auto iter1 = _uid_rid.find(uid);
        if (iter1 == _uid_rid.end())
            return room_ptr();

        // 2. 通过rid获取房间句柄
        int rid = iter1->second;
        auto iter2 = _rooms.find(rid);
        if (iter2 == _rooms.end())
            return room_ptr();
        return iter2->second;
    }

    // 通过房间号来销毁房间
    void destroy_room(int rid)
    {
        // 1. 因为房间是通过room_ ptr来管理的，而room_ ptr会被保存到_ rooms哈希表中，因此销毁房间其实就是销毁哈希表中含有room_ ptr的键值对即可
        // 2. 但销毁房间不仅仅销毁房间就可以了，还需要将哈希表中存储的uid和rid的映射关系也要移除
        room_ptr rp = get_room_by_rid(rid);
        if (rp.get() == nullptr)
            return; // 房间不存在，直接return

        int uid1 = rp->get_black_user();
        int uid2 = rp->get_white_user();

        std::unique_lock<std::mutex> lock(_mtx); // 需要加锁保护哈希表
        _rooms.erase(rid);
        _uid_rid.erase(uid1);
        _uid_rid.erase(uid2);
    }

    // 通过uid来删除房间中的指定用户，如果房间无用户，则销毁房间
    void remove_room_user(int uid)
    {
        room_ptr rp = get_room_by_uid(uid);
        if (rp.get() == nullptr)
            return; // 房间不存在，直接return

        // 某个玩家要退出
        rp->handler_exit(uid);

        // 如果房间没人了，则销毁房间
        if (rp->get_player_num() == 0)
            destroy_room(rp->get_room_id());
    }

private:
    int _next_rid; // 房间号分配器
    user_table *_ut;
    online_manager *_om;

    std::unordered_map<int, room_ptr> _rooms; // 房间号 与 房间的映射关系
    std::unordered_map<int, int> _uid_rid;    // 用户id 与 房间号的映射关系
    std::mutex _mtx;
};

#endif